<!DOCTYPE html>
<html class="writer-html5" lang="zh" >
<head>
  <meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />

  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Ch6-0 使用新版本特性 &mdash; EasyVulkan</title>
      <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
      <link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
      <link rel="stylesheet" href="_static/theme.css" type="text/css" />
  <!--[if lt IE 9]>
    <script src="_static/js/html5shiv.min.js"></script>
  <![endif]-->
  
        <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
        <script src="_static/jquery.js"></script>
        <script src="_static/underscore.js"></script>
        <script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
        <script src="_static/doctools.js"></script>
    <script src="_static/js/theme.js"></script>
    <link rel="index" title="索引" href="genindex.html" />
    <link rel="search" title="搜索" href="search.html" />
    <link rel="next" title="Ch6-1 无图像帧缓冲" href="Ch6-1%20%E6%97%A0%E5%9B%BE%E5%83%8F%E5%B8%A7%E7%BC%93%E5%86%B2.html" />
    <link rel="prev" title="Ch5-3 2D贴图数组" href="Ch5-3%202D%E8%B4%B4%E5%9B%BE%E6%95%B0%E7%BB%84.html" /> 
</head>

<body class="wy-body-for-nav"> 
  <div class="wy-grid-for-nav">
    <nav data-toggle="wy-nav-shift" class="wy-nav-side">
      <div class="wy-side-scroll">
        <div class="wy-side-nav-search" >
            <a href="index.html" class="icon icon-home"> EasyVulkan
            <img src="_static/logo1.png" class="logo" alt="Logo"/>
          </a>
<div role="search">
  <form id="rtd-search-form" class="wy-form" action="search.html" method="get">
    <input type="text" name="q" placeholder="在文档中搜索" />
    <input type="hidden" name="check_keywords" value="yes" />
    <input type="hidden" name="area" value="default" />
  </form>
</div>
        </div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
              <p class="caption" role="heading"><span class="caption-text">第一章 初始化</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch1-0%20%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C.html">Ch1-0 准备工作</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch1-1%20%E5%88%9B%E5%BB%BAGLFW%E7%AA%97%E5%8F%A3.html">Ch1-1 创建GLFW窗口</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch1-2%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B.html">Ch1-2 初始化流程</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch1-3%20%E5%88%9B%E5%BB%BAVK%E5%AE%9E%E4%BE%8B%E4%B8%8E%E9%80%BB%E8%BE%91%E8%AE%BE%E5%A4%87.html">Ch1-3 创建VK实例与逻辑设备</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch1-4%20%E5%88%9B%E5%BB%BA%E4%BA%A4%E6%8D%A2%E9%93%BE.html">Ch1-4 创建交换链</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第二章 绘制一个三角形</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch2-0%20%E4%BB%A3%E7%A0%81%E6%95%B4%E7%90%86%E5%8F%8A%E4%B8%80%E4%BA%9B%E8%BE%85%E5%8A%A9%E7%B1%BB.html">Ch2-0 代码整理及一些辅助类</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch2-1%20Rendering%20Loop.html">Ch2-1 Rendering Loop</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch2-2%20%E5%88%9B%E5%BB%BA%E6%B8%B2%E6%9F%93%E9%80%9A%E9%81%93%E5%92%8C%E5%B8%A7%E7%BC%93%E5%86%B2.html">Ch2-2 创建渲染通道和帧缓冲</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch2-3%20%E5%88%9B%E5%BB%BA%E7%AE%A1%E7%BA%BF%E5%B9%B6%E7%BB%98%E5%88%B6%E4%B8%89%E8%A7%92%E5%BD%A2.html">Ch2-3 创建管线并绘制三角形</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第三章 纵观Vulkan</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch3-1%20%E5%90%8C%E6%AD%A5%E5%8E%9F%E8%AF%AD.html">Ch3-1 同步原语</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-2%20%E5%9B%BE%E5%83%8F%E4%B8%8E%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch3-2 图像与缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-3%20%E7%AE%A1%E7%BA%BF%E5%B8%83%E5%B1%80%E5%92%8C%E7%AE%A1%E7%BA%BF.html">Ch3-3 管线布局和管线</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-4%20%E6%B8%B2%E6%9F%93%E9%80%9A%E9%81%93%E5%92%8C%E5%B8%A7%E7%BC%93%E5%86%B2.html">Ch3-4 渲染通道和帧缓冲</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-5%20%E5%91%BD%E4%BB%A4%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch3-5 命令缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-6%20%E6%8F%8F%E8%BF%B0%E7%AC%A6.html">Ch3-6 描述符</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-7%20%E9%87%87%E6%A0%B7%E5%99%A8.html">Ch3-7 采样器</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-8%20%E6%9F%A5%E8%AF%A2.html">Ch3-8 查询</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第四章 着色器</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch4-1%20%E7%9D%80%E8%89%B2%E5%99%A8%E6%A8%A1%E7%BB%84.html">Ch4-1 着色器模组</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch4-2%20%E9%A1%B6%E7%82%B9%E7%9D%80%E8%89%B2%E5%99%A8.html">Ch4-2 顶点着色器</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch4-3%20%E7%89%87%E6%AE%B5%E7%9D%80%E8%89%B2%E5%99%A8.html">Ch4-3 片段着色器</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch4-4%20%E5%87%A0%E4%BD%95%E7%9D%80%E8%89%B2%E5%99%A8.html">Ch4-4 几何着色器</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第五章 封装常用对象</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch5-0%20VKBase%2B.h.html">Ch5-0 VKBase+.h</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch5-1%20%E5%90%84%E7%A7%8D%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch5-1 各种缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch5-2%202D%E8%B4%B4%E5%9B%BE%E5%8F%8A%E7%94%9F%E6%88%90Mipmap.html">Ch5-2 2D贴图及生成Mipmap</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch5-3%202D%E8%B4%B4%E5%9B%BE%E6%95%B0%E7%BB%84.html">Ch5-3 2D贴图数组</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第六章 进阶Vulkan</span></p>
<ul class="current">
<li class="toctree-l1 current"><a class="current reference internal" href="#">Ch6-0 使用新版本特性</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#id1">获取物理设备特性</a></li>
<li class="toctree-l2"><a class="reference internal" href="#id2">获取物理设备属性和物理设备内存属性</a></li>
<li class="toctree-l2"><a class="reference internal" href="#pnext">设置pNext</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="Ch6-1%20%E6%97%A0%E5%9B%BE%E5%83%8F%E5%B8%A7%E7%BC%93%E5%86%B2.html">Ch6-1 无图像帧缓冲</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch6-2%20%E5%8A%A8%E6%80%81%E6%B8%B2%E6%9F%93.html">Ch6-2 动态渲染</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第七章 基础示例</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch7-1%20%E5%88%9D%E8%AF%86%E9%A1%B6%E7%82%B9%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch7-1 初识顶点缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-2%20%E5%88%9D%E8%AF%86%E7%B4%A2%E5%BC%95%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch7-2 初识索引缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-3%20%E5%88%9D%E8%AF%86%E5%AE%9E%E4%BE%8B%E5%8C%96%E7%BB%98%E5%88%B6.html">Ch7-3 初识实例化绘制</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-4%20%E5%88%9D%E8%AF%86Push%20Constant.html">Ch7-4 初识Push Constant</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-5%20%E5%88%9D%E8%AF%86Uniform%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch7-5 初识Uniform缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-6%20%E6%8B%B7%E8%B4%9D%E5%9B%BE%E5%83%8F%E5%88%B0%E5%B1%8F%E5%B9%95.html">Ch7-6 拷贝图像到屏幕</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-7%20%E4%BD%BF%E7%94%A8%E8%B4%B4%E5%9B%BE.html">Ch7-7 使用贴图</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第八章 简单示例</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch8-1%20%E7%A6%BB%E5%B1%8F%E6%B8%B2%E6%9F%93.html">Ch8-1 离屏渲染</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch8-2%20%E6%B7%B1%E5%BA%A6%E6%B5%8B%E8%AF%95%E5%92%8C%E6%B7%B1%E5%BA%A6%E5%8F%AF%E8%A7%86%E5%8C%96.html">Ch8-2 深度测试和深度可视化</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch8-3%20%E5%BB%B6%E8%BF%9F%E6%B8%B2%E6%9F%93.html">Ch8-3 延迟渲染</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch8-4%20%E9%A2%84%E4%B9%98Alpha.html">Ch8-4 预乘Alpha</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch8-5%20sRGB%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4%E4%B8%8E%E5%BC%80%E5%90%AFHDR.html">Ch8-5 sRGB色彩空间与开启HDR</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">附录</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ap1-1%20%E8%BF%90%E8%A1%8C%E6%9C%9F%E7%BC%96%E8%AF%91GLSL.html">Ap1-1 运行期编译GLSL</a></li>
</ul>

        </div>
      </div>
    </nav>

    <section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
          <i data-toggle="wy-nav-top" class="fa fa-bars"></i>
          <a href="index.html">EasyVulkan</a>
      </nav>

      <div class="wy-nav-content">
        <div class="rst-content">
          <div role="navigation" aria-label="Page navigation">
  <ul class="wy-breadcrumbs">
      <li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
      <li>Ch6-0 使用新版本特性</li>
      <li class="wy-breadcrumbs-aside">
      </li>
  </ul>
  <hr/>
</div>
          <div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
           <div itemprop="articleBody">
             
  <section id="ch6-0">
<h1>Ch6-0 使用新版本特性<a class="headerlink" href="#ch6-0" title="Permalink to this heading"></a></h1>
<p>
    本章的示例代码参见：<a href="https://github.com/EasyVulkan/EasyVulkan.github.io/tree/main/solution/EasyVulkan_Ch6">EasyVulkan_Ch6</a>
    <br>
    本节的示例代码参见：<a href="https://github.com/EasyVulkan/EasyVulkan.github.io/tree/main/solution/EasyVulkan_Ch6/VKBase.h">VKBase.h</a>
</p>
<p>
    先前（以及之后第七章）基本只使用了Vulkan1.0的版本特性，本章会对一些新版本特性进行介绍。
</p>
<p>
    前情提要，先前已书写了用于获取版本号的函数<span class="type">graphicsBase</span>::<span class="fn">UseLatestApiVersion</span>()：
</p>
<pre class="code">
<span class="type">VkResult</span> <span class="fn">UseLatestApiVersion</span>() {
    <span class="kw">if</span> (<span class="fn">vkGetInstanceProcAddr</span>(<span class="mcr">VK_NULL_HANDLE</span>, <span class="str">&quot;vkEnumerateInstanceVersion&quot;</span>))
        <span class="kw">return</span> <span class="fn">vkEnumerateInstanceVersion</span>(&amp;apiVersion);
    <span class="kw">return</span> <span class="enum">VK_SUCCESS</span>;
}
</pre><section id="id1">
<h2>获取物理设备特性<a class="headerlink" href="#id1" title="Permalink to this heading"></a></h2>
<p>
    先前在<span class="type">graphicsBase</span>::<span class="fn">CreateDevice</span>(...)中获取了物理设备特性<span class="type">VkPhysicalDeviceFeatures</span>，但并没有保存到成员变量。考虑到可能会有需要检验物理设备特性的情况，需要将其保存下来。
    <br>
    <span class="type">VkPhysicalDeviceFeatures</span>没有可扩展性，在Vulkan1.0以上版本，使用<a href="https://renderdoc.org/vkspec_chunked/chap46.html#VkPhysicalDeviceFeatures2">VkPhysicalDeviceFeatures2</a>结构体，它包含原有的<span class="type">VkPhysicalDeviceFeatures</span>，并包含一个pNext指针：
</p>
<table class="docutils align-default">
    <colgroup>
        <col style="width: 30%">
        <col style="width: 70%">
    </colgroup>
    <thead>
        <tr class="row-odd">
            <th class="head" colspan="2"><p><span class="kw">struct</span> <a href="https://renderdoc.org/vkspec_chunked/chap46.html#VkPhysicalDeviceFeatures2">VkPhysicalDeviceFeatures2</a> 的成员说明</p></th>
        </tr>
    </thead>
    <tbody>
        <tr class="row-even">
            <td><p><span class="type">VkStructureType</span> sType</p></td>
            <td><p>结构体的类型，本处必须是<span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2</span></p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="kw">const void</span>* pNext</p></td>
            <td><p>如有必要，指向一个用于扩展该结构体的结构体</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">VkPhysicalDeviceFeatures</span> features</p></td>
            <td><p>Vulkan1.0中的物理设备特性</p></td>
        </tr>
    </tbody>
</table>
<p>
    在<span class="type">graphicsBase</span>中加入以下内容：
</p>
<pre class="code">
    <span class="type">VkPhysicalDeviceFeatures2</span> physicalDeviceFeatures;
    <span class="type">VkPhysicalDeviceVulkan11Features</span> physicalDeviceVulkan11Features; <span class="cmt">//Provided by VK_API_VERSION_1_2</span>
    <span class="type">VkPhysicalDeviceVulkan12Features</span> physicalDeviceVulkan12Features;
    <span class="type">VkPhysicalDeviceVulkan13Features</span> physicalDeviceVulkan13Features;
    <span class="cmt">//获取物理设备特性</span>
    <span class="kw">void</span> <span class="type">GetPhysicalDeviceFeatures</span>() {
        <span class="cmt">/*待填充*/</span>
    }
<span class="kw">public</span>:
    <span class="kw">constexpr const</span> <span class="type">VkPhysicalDeviceFeatures2</span>&amp; <span class="fn">PhysicalDeviceFeatures</span>() <span class="kw">const</span> { <span class="kw">return</span> physicalDeviceFeatures.features; }
    <span class="kw">constexpr const</span> <span class="type">VkPhysicalDeviceVulkan11Features</span>&amp; <span class="fn">PhysicalDeviceVulkan11Features</span>() <span class="kw">const</span> { <span class="kw">return</span> physicalDeviceVulkan11Features; }
    <span class="kw">constexpr const</span> <span class="type">VkPhysicalDeviceVulkan12Features</span>&amp; <span class="fn">PhysicalDeviceVulkan12Features</span>() <span class="kw">const</span> { <span class="kw">return</span> physicalDeviceVulkan12Features; }
    <span class="kw">constexpr const</span> <span class="type">VkPhysicalDeviceVulkan13Features</span>&amp; <span class="fn">PhysicalDeviceVulkan13Features</span>() <span class="kw">const</span> { <span class="kw">return</span> physicalDeviceVulkan13Features; }
</pre>
<ul>
    <li>
        <p>
           Vulkan1.1版本中的各种新增特性对应其各自独立的结构体，<span class="type">VkPhysicalDeviceVulkan11Features</span>整合了这些特性，但它是Vulkan1.2版本中的结构体。
        </p>
    </li>
</ul>
<p>
    填充<span class="type">GetPhysicalDeviceFeatures</span>()，首先判定版本，若Vulkan版本未到1.1，直接获取1.0版本中的全部特性：
</p>
<pre class="code">
<span class="kw">void</span> <span class="type">GetPhysicalDeviceFeatures</span>() {
    <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_1</span>) {
        <span class="cmt">//不管之后需不需要，先把所有结构体名称填完</span>
        physicalDeviceFeatures = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2</span> };
        physicalDeviceVulkan11Features = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES</span> };
        physicalDeviceVulkan12Features = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES</span> };
        physicalDeviceVulkan13Features = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES</span> };
        <span class="cmt">/*待后续填充*/</span>
    }
    <span class="kw">else</span>
        <span class="fn">vkGetPhysicalDeviceFeatures</span>(physicalDevice, &amp;physicalDeviceFeatures.features);
}
</pre>
<p>
    如果当前设备的Vulkan最高版本没到1.2，则理论上你不能让<span class="type">VkPhysicalDeviceFeatures2</span>::pNext指向<span class="type">VkPhysicalDeviceVulkan11Features</span>，要获取新增特性，需要将<span class="type">VkPhysicalDeviceVulkan11Features</span>中各种特性对应的结构体按需要逐一加入pNext链，这里就不做赘述了。
    <br>
    于是版本号只到1.1而没到1.2的话，这里姑且只获取1.0版本的全部特性：
</p>
<pre class="code">
<span class="kw">void</span> <span class="type">GetPhysicalDeviceFeatures</span>() {
    <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_1</span>) {
        physicalDeviceFeatures = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2</span> };
        physicalDeviceVulkan11Features = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES</span> };
        physicalDeviceVulkan12Features = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES</span> };
        physicalDeviceVulkan13Features = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES</span> };
        <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_2</span>) {
            <span class="cmt">/*待后续填充*/</span>
        }
        <span class="fn">vkGetPhysicalDeviceFeatures2</span>(physicalDevice, &amp;physicalDeviceFeatures);
    }
    <span class="kw">else</span>
        <span class="fn">vkGetPhysicalDeviceFeatures</span>(physicalDevice, &amp;physicalDeviceFeatures.features);
}
</pre>
<p>
    版本号到1.2的话，接上<span class="type">VkPhysicalDeviceVulkan11Features</span>和<span class="type">VkPhysicalDeviceVulkan12Features</span>。<br>
    版本号到1.3的话，接上<span class="type">VkPhysicalDeviceVulkan13Features</span>。
</p>
<pre class="code">
<span class="kw">void</span> <span class="type">GetPhysicalDeviceFeatures</span>() {
    <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_1</span>) {
        physicalDeviceFeatures = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2</span> };
        physicalDeviceVulkan11Features = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES</span> };
        physicalDeviceVulkan12Features = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES</span> };
        physicalDeviceVulkan13Features = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES</span> };
        <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_2</span>) {
            physicalDeviceFeatures.pNext = &amp;physicalDeviceVulkan11Features;
            physicalDeviceVulkan11Features.pNext = &amp;physicalDeviceVulkan12Features;
            <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_3</span>)
                physicalDeviceVulkan12Features.pNext = &amp;physicalDeviceVulkan13Features;
        }
        <span class="fn">vkGetPhysicalDeviceFeatures2</span>(physicalDevice, &amp;physicalDeviceFeatures);
    }
    <span class="kw">else</span>
        <span class="fn">vkGetPhysicalDeviceFeatures</span>(physicalDevice, &amp;physicalDeviceFeatures.features);
}
</pre></section>
<section id="id2">
<h2>获取物理设备属性和物理设备内存属性<a class="headerlink" href="#id2" title="Permalink to this heading"></a></h2>
<p>
    类似物理设备特性，物理设备属性和物理设备内存属性在后续版本中也有相应的可扩展类型<a href="https://renderdoc.org/vkspec_chunked/chap5.html#VkPhysicalDeviceProperties2">VkPhysicalDeviceProperties2</a>和<a href="https://renderdoc.org/vkspec_chunked/chap11.html#VkPhysicalDeviceMemoryProperties2">VkPhysicalDeviceMemoryProperties2</a>：
</p>
<table class="docutils align-default">
    <colgroup>
        <col style="width: 30%">
        <col style="width: 70%">
    </colgroup>
    <thead>
        <tr class="row-odd">
            <th class="head" colspan="2"><p><span class="kw">struct</span> <a href="https://renderdoc.org/vkspec_chunked/chap5.html#VkPhysicalDeviceProperties2">VkPhysicalDeviceProperties2</a> 的成员说明</p></th>
        </tr>
    </thead>
    <tbody>
        <tr class="row-even">
            <td><p><span class="type">VkStructureType</span> sType</p></td>
            <td><p>结构体的类型，本处必须是<span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2</span></p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="kw">const void</span>* pNext</p></td>
            <td><p>如有必要，指向一个用于扩展该结构体的结构体</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">VkPhysicalDeviceProperties</span> properties</p></td>
            <td><p>Vulkan1.0中的物理设备属性</p></td>
        </tr>
    </tbody>
</table>
<table class="docutils align-default">
    <colgroup>
        <col style="width: 30%">
        <col style="width: 70%">
    </colgroup>
    <thead>
        <tr class="row-odd">
            <th class="head" colspan="2"><p><span class="kw">struct</span> <a href="https://renderdoc.org/vkspec_chunked/chap11.html#VkPhysicalDeviceMemoryProperties2">VkPhysicalDeviceMemoryProperties2</a> 的成员说明</p></th>
        </tr>
    </thead>
    <tbody>
        <tr class="row-even">
            <td><p><span class="type">VkStructureType</span> sType</p></td>
            <td><p>结构体的类型，本处必须是<span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2</span></p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="kw">const void</span>* pNext</p></td>
            <td><p>如有必要，指向一个用于扩展该结构体的结构体（到1.3为止只能指向<a href="https://renderdoc.org/vkspec_chunked/chap11.html#VkPhysicalDeviceMemoryBudgetPropertiesEXT">VkPhysicalDeviceMemoryBudgetPropertiesEXT</span>）</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">VkPhysicalDeviceMemoryProperties</span> memoryProperties</p></td>
            <td><p>Vulkan1.0中的物理设备内存属性</p></td>
        </tr>
    </tbody>
</table>
<p>
    去掉原先<span class="type">graphicsBase</span>中的成员变量声明<code><span class="type">VkPhysicalDeviceProperties</span> physicalDeviceProperties;</code>和<code><span class="type">VkPhysicalDeviceMemoryProperties</span> physicalDeviceMemoryProperties;</code>及相应Getter函数，加入以下内容：
</p>
<pre class="code">
    <span class="type">VkPhysicalDeviceProperties2</span> physicalDeviceProperties;
    <span class="type">VkPhysicalDeviceVulkan11Properties</span> physicalDeviceVulkan11Properties; <span class="cmt">//Provided by VK_API_VERSION_1_2</span>
    <span class="type">VkPhysicalDeviceVulkan12Properties</span> physicalDeviceVulkan12Properties;
    <span class="type">VkPhysicalDeviceVulkan13Properties</span> physicalDeviceVulkan13Properties;
    <span class="type">VkPhysicalDeviceMemoryProperties2</span> physicalDeviceMemoryProperties;   <span class="cmt">//截至Vulkan1.3为止，不存在VkPhysicalDeviceVulkan11MemoryProperties等结构体</span>
    <span class="cmt">//获取物理设备属性，逻辑跟GetPhysicalDeviceFeatures()一样，解说略</span>
    <span class="kw">void</span> <span class="type">GetPhysicalDeviceProperties</span>() {
        <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_1</span>) {
            physicalDeviceProperties = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2</span> };
            physicalDeviceVulkan11Properties = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES</span> };
            physicalDeviceVulkan12Properties = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES</span> };
            physicalDeviceVulkan13Properties = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_PROPERTIES</span> };
            <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_2</span>) {
                physicalDeviceProperties.pNext = &amp;physicalDeviceVulkan11Properties;
                physicalDeviceVulkan11Properties.pNext = &amp;physicalDeviceVulkan12Properties;
                <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_3</span>) {
                    physicalDeviceVulkan12Properties.pNext = &amp;physicalDeviceVulkan13Properties;
            }
            <span class="fn">vkGetPhysicalDeviceProperties2</span>(physicalDevice, &amp;physicalDeviceProperties);
            physicalDeviceMemoryProperties = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2</span> };
            <span class="fn">vkGetPhysicalDeviceMemoryProperties2</span>(physicalDevice, &amp;physicalDeviceMemoryProperties);
        }
        <span class="kw">else</span>
            <span class="fn">vkGetPhysicalDeviceProperties</span>(physicalDevice, &amp;physicalDeviceProperties.properties),
            <span class="fn">vkGetPhysicalDeviceMemoryProperties</span>(physicalDevice, &amp;physicalDeviceMemoryProperties.memoryProperties);
    }
<span class="kw">public</span>:
    <span class="kw">constexpr const</span> <span class="type">VkPhysicalDeviceProperties2</span>&amp; <span class="fn">PhysicalDeviceProperties</span>() <span class="kw">const</span> { <span class="kw">return</span> physicalDeviceProperties.properties; }
    <span class="kw">constexpr const</span> <span class="type">VkPhysicalDeviceVulkan11Properties</span>&amp; <span class="fn">PhysicalDeviceVulkan11Properties</span>() <span class="kw">const</span> { <span class="kw">return</span> physicalDeviceVulkan11Properties; }
    <span class="kw">constexpr const</span> <span class="type">VkPhysicalDeviceVulkan12Properties</span>&amp; <span class="fn">PhysicalDeviceVulkan12Properties</span>() <span class="kw">const</span> { <span class="kw">return</span> physicalDeviceVulkan12Properties; }
    <span class="kw">constexpr const</span> <span class="type">VkPhysicalDeviceVulkan13Properties</span>&amp; <span class="fn">PhysicalDeviceVulkan13Properties</span>() <span class="kw">const</span> { <span class="kw">return</span> physicalDeviceVulkan13Properties; }
    <span class="kw">constexpr const</span> <span class="type">VkPhysicalDeviceMemoryProperties2</span>&amp; <span class="fn">PhysicalDeviceMemoryProperties</span>() <span class="kw">const</span> { <span class="kw">return</span> physicalDeviceProperties.properties; }
</pre></section>
<section id="pnext">
<h2>设置pNext<a class="headerlink" href="#pnext" title="Permalink to this heading"></a></h2>
<p>
    我在此前的教程中几乎完全忽略了各种结构体的pNext成员。
    <br>
    一个结构体的pNext成员指向下一个结构体，下一个结构体的pNext又指向再下一个结构体，这种链表在Vulkan官方标准中被称为pNext链（pNext chain）。
</p>
<p>
    若要使用一些由扩展提供的功能特性，需要将涉及的结构体加入pNext链来查询并开启特性，比如若要开启光追的话，需要在<span class="type">VkPhysicalDeviceFeatures2</span>的后续pNext链中加入<span class="type">VkPhysicalDeviceAccelerationStructureFeaturesKHR</span>和<span class="type">VkPhysicalDeviceAccelerationStructureFeaturesKHR</span>。
    <br>
    而在你设计程序时，可能会把不同功能的部分放在各自八竿子打不着的代码中，不同的功能可能需要开启相同的设备特性。定义一个<span class="type">graphicsBase</span>的静态成员函数<span class="sfn">SetPNext</span>(...)来确保正确处理pNext链，旨在让结构体能以任何顺序被加入pNext链并查重，以及定义其他相关的成员变量和函数：
</p>
<pre class="code">
    <span class="kw">void</span>* pNext_instanceCreateInfo;
    <span class="kw">void</span>* pNext_deviceCreateInfo;
    <span class="kw">void</span>* pNext_physicalDeviceFeatures;
    <span class="kw">void</span>* pNext_physicalDeviceProperties;
    <span class="kw">void</span>* pNext_physicalDeviceMemoryProperties;
    <span class="kw">static void</span>** <span class="sfn">SetPNext</span>(<span class="kw">void</span>*&amp; <span class="par">pBegin</span>, <span class="kw">void</span>* <span class="par">pNext</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span> = <span class="kw">false</span>) {
        <span class="cmt">/*待填充*/</span>
    }
<span class="kw">public</span>:
    <span class="cmt">//以下各函数将指定的next结构体添加到各个pNext链末尾</span>
    <span class="kw">void</span> <span class="fn">AddNextStructure_InstanceCreateInfo</span>(<span class="kw">auto</span>&amp; <span class="par">next</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span> = <span class="kw">false</span>) {
        <span class="sfn">SetPNext</span>(pNext_instanceCreateInfo, &amp;<span class="par">next</span>, <span class="par">allowDuplicate</span>);
    }
    <span class="kw">void</span> <span class="fn">AddNextStructure_DeviceCreateInfo</span>(<span class="kw">auto</span>&amp; <span class="par">next</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span> = <span class="kw">false</span>) {
        <span class="sfn">SetPNext</span>(pNext_deviceCreateInfo, &amp;<span class="par">next</span>, <span class="par">allowDuplicate</span>);
    }
    <span class="kw">void</span> <span class="fn">AddNextStructure_PhysicalDeviceFeatures</span>(<span class="kw">auto</span>&amp; <span class="par">next</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span> = <span class="kw">false</span>) {
        <span class="sfn">SetPNext</span>(pNext_physicalDeviceFeatures, &amp;<span class="par">next</span>, <span class="par">allowDuplicate</span>);
    }
    <span class="kw">void</span> <span class="fn">AddNextStructure_PhysicalDeviceProperties</span>(<span class="kw">auto</span>&amp; <span class="par">next</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span> = <span class="kw">false</span>) {
        <span class="sfn">SetPNext</span>(pNext_physicalDeviceProperties, &amp;<span class="par">next</span>, <span class="par">allowDuplicate</span>);
    }
    <span class="kw">void</span> <span class="fn">AddNextStructure_PhysicalDeviceMemoryProperties</span>(<span class="kw">auto</span>&amp; <span class="par">next</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span> = <span class="kw">false</span>) {
        <span class="sfn">SetPNext</span>(pNext_physicalDeviceMemoryProperties, &amp;<span class="par">next</span>, <span class="par">allowDuplicate</span>);
    }
    <span class="kw">void</span> <span class="fn">AddNextStructure_SwapchainCreateInfo</span>(<span class="kw">auto</span>&amp; <span class="par">next</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span> = <span class="kw">false</span>) {
        <span class="sfn">SetPNext</span>(<span class="kw">const_cast</span>&lt;<span class="kw">void</span>*&amp;&gt;(swapchainCreateInfo.pNext), &amp;<span class="par">next</span>, <span class="par">allowDuplicate</span>);
    }
</pre>
<ul>
    <li>
        <p>
            各成员变量名已经顾名思义地说明了其用处。<span class="type">VkSwapchainCreateInfoKHR</span>则因需要重建交换链而保存为了成员变量，直接使用其中的pNext成员（除非你不喜欢<span class="kw">const_cast</span>）。
        </p>
    </li>
    <li>
        <p>
            <span class="sfn">SetPNext</span>(...)的参数<span class="par">allowDuplicate</span>指定是否允许某种结构体重复出现。对于某些函数，某些结构体可以在向其提供的pNext链中出现多个，比如<span class="type">VkDeviceCreateInfo</span>的pNext链中，允许有多个<span class="type">VkDeviceDeviceMemoryReportCreateInfoEXT</span>和<span class="type">VkDevicePrivateDataCreateInfo</span>。
        </p>
    </li>
</ul>
<p>
    以引用传递的<span class="par">pBegin</span>是向<span class="sfn">SetPNext</span>(...)提供的pNext链中的首个指针，参数<span class="par">pNext</span>指向要被加入该pNext链的结构体。
    <br>
    <span class="sfn">SetPNext</span>(...)的返回类型是<span class="kw">void</span>**，这个无法一上来就解释清楚，但我打算让函数在给定的<span class="par">pNext</span>参数无法被接到以<span class="par">pBegin</span>起始的pNext链时，返回<span class="kw">nullptr</span>。
</p>
<pre class="code">
<span class="kw">static void</span>** <span class="sfn">SetPNext</span>(<span class="kw">void</span>*&amp; <span class="par">pBegin</span>, <span class="kw">void</span>* <span class="par">pNext</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span> = <span class="kw">false</span>) {
    <span class="kw">struct</span> <span class="type">vkStructureHead</span> {
        <span class="type">VkStructureType</span> sType;
        <span class="kw">void</span>* pNext;
    };
    <span class="kw">if</span> (!<span class="par">pNext</span>)
        <span class="kw">return nullptr</span>;
    <span class="cmt">/*待后续填充*/</span>
}
</pre>
<ul>
    <li>
        <p>
            pNext链中各个结构体都有不同的构造，但开头是相同的，可以通过<span class="kw">reinterpret_cast</span>&lt;<span class="type">vkStructureHead</span>&gt;(...)，获得<span class="par">pBegin</span>和<span class="par">pNext</span>所指向结构体的sType和pNext成员。
        </p>
    </li>
    <li>
        <p>
            因为pNext链尾部的指针本就是<span class="kw">nullptr</span>，若传入函数的参数<span class="par">pNext</span>是<span class="kw">nullptr</span>，不必有后续执行，直接返回<span class="kw">nullptr</span>。
        </p>
    </li>
</ul>
<p>
    既然pNext链是链表，很容易想到使用递归来处理相关逻辑。<span class="sfn">SetPNext</span>(...)剩下的部分我打算使用一个能自我调用的lambda表达式来实现：
</p>
<pre class="code">
<span class="kw">static void</span>** <span class="sfn">SetPNext</span>(<span class="kw">void</span>*&amp; <span class="par">pBegin</span>, <span class="kw">void</span>* <span class="par">pNext</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span> = <span class="kw">false</span>) {
    <span class="kw">struct</span> <span class="type">vkStructureHead</span> {
        <span class="type">VkStructureType</span> sType;
        <span class="kw">void</span>* pNext;
    };
    <span class="kw">if</span> (!<span class="par">pNext</span>)
        <span class="kw">return nullptr</span>;
    <span class="kw">auto</span> SetPNext_Internal = [](<span class="kw">this auto</span>&amp;&amp; <span class="par">self</span>, <span class="kw">void</span>*&amp; <span class="par">pCurrentNode</span>, <span class="kw">void</span>* <span class="par">pNext</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span>)-&gt;<span class="kw">void</span>** {
        <span class="cmt">/*待后续填充*/</span>
    };
    <span class="kw">return</span> SetPNext_Internal(<span class="par">pBegin</span>, <span class="par">pNext</span>, <span class="par">allowDuplicate</span>);
}
</pre>
<ul>
    <li>
        <p>
            <code><span class="kw">this auto</span>&amp;&amp;</code>这个写法叫lambda的显式对象形参（explicit object parameter），是<a href="https://en.cppreference.com/w/cpp/language/function#explicit_object_parameter">C++23的语法</a>。显式对象参数对于类成员函数和lambda有不同的含义，这里的lambda显式对象参数<span class="var">self</span>指代lambda自身。Lambda表达式属于函数对象，由于对象的初始化器中不能递归地提及对象本身的名称，在C++23以前，实现递归lambda的方式要略微繁琐一些。
        </p>
    </li>
</ul>
<p>
    填充<span class="var">SetPNext_Internal</span>(...)，先只用关心<span class="par">pCurrentNode</span>跟<span class="par">pNext</span>两个参数的关系，这里<span class="par">pCurrentNode</span>是pNext链中的任意节点（即某一结构体的pNext成员），<span class="par">pNext</span>则跟传入<span class="sfn">SetPNext</span>(...)的<span class="par">pNext</span>数值一致。
    <br>
    显然，若<span class="par">pCurrentNode</span>跟<span class="par">pNext</span>一致，则说明<span class="par">pNext</span>指定的结构体已在pNext链中，则函数执行失败（pNext链中可能允许某种结构体出现多个，但特定对象只能出现一次，否则理所当然地会出现无限递归链表）。
    <br>
    然后，若<span class="par">pCurrentNode</span>为<span class="kw">nullptr</span>，说明达到了链表尾端，将<span class="par">pCurrentNode</span>赋值为<span class="par">pNext</span>，成功时的返回值是<span class="par">pCurrentNode</span>的地址，即被赋值为了<span class="par">pNext</span>的那个节点的地址，用处到本节末尾再解释。
</p>
<pre class="code">
<span class="kw">static void</span>** <span class="sfn">SetPNext</span>(<span class="kw">void</span>*&amp; <span class="par">pBegin</span>, <span class="kw">void</span>* <span class="par">pNext</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span> = <span class="kw">false</span>) {
    <span class="kw">struct</span> <span class="type">vkStructureHead</span> {
        <span class="type">VkStructureType</span> sType;
        <span class="kw">void</span>* pNext;
    };
    <span class="kw">if</span> (!<span class="par">pNext</span>)
        <span class="kw">return nullptr</span>;
    <span class="kw">auto</span> SetPNext_Internal = [](<span class="kw">this auto</span>&amp;&amp; <span class="par">self</span>, <span class="kw">void</span>*&amp; <span class="par">pCurrentNode</span>, <span class="kw">void</span>* <span class="par">pNext</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span>)-&gt;<span class="kw">void</span>** {
        <span class="kw">if</span> (<span class="par">pCurrentNode</span> == <span class="par">pNext</span>)
            <span class="kw">return nullptr</span>;
        <span class="kw">if</span> (<span class="par">pCurrentNode</span>)
            <span class="cmt">/*待后续填充*/</span>
        <span class="kw">else</span>
            <span class="kw">return</span> &amp;(<span class="par">pCurrentNode</span> = <span class="par">pNext</span>);
    };
    <span class="kw">return</span> SetPNext_Internal(<span class="par">pBegin</span>, <span class="par">pNext</span>, <span class="par">allowDuplicate</span>);
}
</pre>
<p>
    若<span class="par">allowDuplicate</span>为<span class="kw">false</span>，比较<span class="par">pCurrentNode</span>和<span class="par">pNext</span>指向结构体的sType成员，一致则函数执行失败：
</p>
<pre class="code">
<span class="kw">auto</span> SetPNext_Internal = [](<span class="kw">this auto</span>&amp;&amp; <span class="par">self</span>, <span class="kw">void</span>*&amp; <span class="par">pCurrentNode</span>, <span class="kw">void</span>* <span class="par">pNext</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span>)-&gt;<span class="kw">void</span>** {
    <span class="kw">if</span> (<span class="par">pCurrentNode</span> == <span class="par">pNext</span>)
        <span class="kw">return nullptr</span>;
    <span class="kw">if</span> (<span class="par">pCurrentNode</span>)
        <span class="kw">if</span> (!<span class="par">allowDuplicate</span> &amp;&amp;
            <span class="kw">reinterpret_cast</span>&lt;<span class="type">vkStructureHead</span>&gt;(<span class="par">pCurrentNode</span>)-&gt;sType == <span class="kw">reinterpret_cast</span>&lt;<span class="type">vkStructureHead</span>&gt;(<span class="par">pNext</span>)-&gt;sType)
            <span class="kw">return nullptr</span>;
        <span class="kw">else</span>
            <span class="cmt">/*待后续填充*/</span>
    <span class="kw">else</span>
        <span class="kw">return</span> &amp;(<span class="par">pCurrentNode</span> = <span class="par">pNext</span>);
};
</pre>
<p>
    还剩的一个<span class="kw">else</span>分支就是<span class="par">pCurrentNode</span>非空并且满足查重条件的情况，这时候就继续向链表后续节点迭代，后续节点即当前节点<span class="par">pCurrentNode</span>所指结构体的pNext成员：
</p>
<pre class="code">
<span class="kw">static void</span>** <span class="sfn">SetPNext</span>(<span class="kw">void</span>*&amp; <span class="par">pBegin</span>, <span class="kw">void</span>* <span class="par">pNext</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span> = <span class="kw">false</span>) {
    <span class="kw">struct</span> <span class="type">vkStructureHead</span> {
        <span class="type">VkStructureType</span> sType;
        <span class="kw">void</span>* pNext;
    };
    <span class="kw">if</span> (!<span class="par">pNext</span>)
        <span class="kw">return nullptr</span>;
    <span class="kw">auto</span> SetPNext_Internal = [](<span class="kw">this auto</span>&amp;&amp; <span class="par">self</span>, <span class="kw">void</span>*&amp; <span class="par">pCurrentNode</span>, <span class="kw">void</span>* <span class="par">pNext</span>, <span class="kw">bool</span> <span class="par">allowDuplicate</span>)-&gt;<span class="kw">void</span>** {
        <span class="kw">if</span> (<span class="par">pCurrentNode</span> == <span class="par">pNext</span>)
            <span class="kw">return nullptr</span>;
        <span class="kw">if</span> (<span class="par">pCurrentNode</span>)
            <span class="kw">if</span> (!<span class="par">allowDuplicate</span> &amp;&amp;
                <span class="kw">reinterpret_cast</span>&lt;<span class="type">vkStructureHead</span>&gt;(<span class="par">pCurrentNode</span>)-&gt;sType == <span class="kw">reinterpret_cast</span>&lt;<span class="type">vkStructureHead</span>&gt;(<span class="par">pNext</span>)-&gt;sType)
                <span class="kw">return nullptr</span>;
            <span class="kw">else</span>
                <span class="kw">return</span> self(<span class="kw">reinterpret_cast</span>&lt;<span class="type">vkStructureHead</span>&gt;(<span class="par">pCurrentNode</span>)-&gt;pNext, <span class="par">pNext</span>, <span class="par">allowDuplicate</span>);
        <span class="kw">else</span>
            <span class="kw">return</span> &amp;(<span class="par">pCurrentNode</span> = <span class="par">pNext</span>);
    };
    <span class="kw">return</span> SetPNext_Internal(<span class="par">pBegin</span>, <span class="par">pNext</span>, <span class="par">allowDuplicate</span>);
}
</pre>
<p>
    接着考虑更改创建Vulkan实例、逻辑设备、交换链的函数。
    <br>
    24.03.20 注：原本在Ch1中，是在<span class="fn">CreateInstance</span>(...)和<span class="fn">CreateDevice</span>(...)等函数的参数中传入创建信息的pNext，在写本文时对以往教程和示例代码一并修改，去掉了这些函数参数中的<code><span class="kw">const void</span>* <span class="par">pNext</span></code>。在此日期前没读过我的教程的话不必在意。
    <br>
    <span class="fn">CreateSwapchain</span>(...)不用修改，<span class="fn">CreateInstance</span>(...)中只要赋值创建信息的pNext成员就行：
</p>
<pre class="code">
<span class="type">VkResult</span> <span class="fn">CreateInstance</span>(<span class="type">VkInstanceCreateFlags</span> <span class="par">flags</span> = 0) {
    <span class="cmt">/*...*/</span>
    <span class="type">VkInstanceCreateInfo</span> instanceCreateInfo = {
        .sType = <span class="enum">VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO</span>,
        .pNext = pNext_instanceCreateInfo, <span class="cmt">//新增</span>
        .flags = <span class="par">flags</span>,
        .pApplicationInfo = &amp;applicatianInfo,
        .enabledLayerCount = <span class="type">uint32_t</span>(instanceLayers.<span class="fn">size</span>()),
        .ppEnabledLayerNames = instanceLayers.data(),
        .enabledExtensionCount = <span class="type">uint32_t</span>(instanceExtensions.<span class="fn">size</span>()),
        .ppEnabledExtensionNames = instanceExtensions.<span class="fn">data</span>()
    };
    <span class="cmt">/*...*/</span>
}
</pre>
<p>
    更改<span class="fn">CreateDevice</span>(...)前，简单修改<span class="type">GetPhysicalDeviceFeaturess</span>()和<span class="type">GetPhysicalDeviceProperties</span>()，调用<span class="sfn">SetPNext</span>(...)：
</p>
<pre class="code">
<span class="kw">void</span> <span class="type">GetPhysicalDeviceFeatures</span>() {
    <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_1</span>) {
        physicalDeviceFeatures = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2</span> };
        physicalDeviceVulkan11Features = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES</span> };
        physicalDeviceVulkan12Features = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES</span> };
        physicalDeviceVulkan13Features = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES</span> };
        <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_2</span>) {
            physicalDeviceFeatures.pNext = &amp;physicalDeviceVulkan11Features;
            physicalDeviceVulkan11Features.pNext = &amp;physicalDeviceVulkan12Features;
            <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_3</span>)
                physicalDeviceVulkan12Features.pNext = &amp;physicalDeviceVulkan13Features;
        }
        <span class="sfn">SetPNext</span>(physicalDeviceFeatures.pNext, pNext_physicalDeviceFeatures);
        <span class="fn">vkGetPhysicalDeviceFeatures2</span>(physicalDevice, &amp;physicalDeviceFeatures);
    }
    <span class="kw">else</span>
        <span class="fn">vkGetPhysicalDeviceFeatures</span>(physicalDevice, &amp;physicalDeviceFeatures.features);
}
<span class="kw">void</span> <span class="type">GetPhysicalDeviceProperties</span>() {
    <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_1</span>) {
        physicalDeviceProperties = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2</span> };
        physicalDeviceVulkan11Properties = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES</span> };
        physicalDeviceVulkan12Properties = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES</span> };
        physicalDeviceVulkan13Properties = { <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_PROPERTIES</span> };
        <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_2</span>) {
            physicalDeviceProperties.pNext = &amp;physicalDeviceVulkan11Properties;
            physicalDeviceVulkan11Properties.pNext = &amp;physicalDeviceVulkan12Properties;
            <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_3</span>) {
                physicalDeviceVulkan12Properties.pNext = &amp;physicalDeviceVulkan13Properties;
        }
        <span class="sfn">SetPNext</span>(physicalDeviceProperties.pNext, pNext_physicalDeviceProperties);
        <span class="fn">vkGetPhysicalDeviceProperties2</span>(physicalDevice, &amp;physicalDeviceProperties);
        physicalDeviceMemoryProperties = {
            <span class="enum">VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2</span>,
            pNext_physicalDeviceMemoryProperties <span class="cmt">//没必要调用SetPNext(...)</span>
        };
        <span class="fn">vkGetPhysicalDeviceMemoryProperties2</span>(physicalDevice, &amp;physicalDeviceMemoryProperties);
    }
    <span class="kw">else</span>
        <span class="fn">vkGetPhysicalDeviceProperties</span>(physicalDevice, &amp;physicalDeviceProperties.properties),
        <span class="fn">vkGetPhysicalDeviceMemoryProperties</span>(physicalDevice, &amp;physicalDeviceMemoryProperties.memoryProperties);
}
</pre>
<p>
    更改<span class="fn">CreateDevice</span>(...)，类似<span class="fn">CreateInstance</span>(...)，赋值创建信息的pNext成员，然后将获取设备特性、属性、内存属性的部分用前述封装好的函数替代：
</p>
<pre class="code">
<span class="type">VkResult</span> <span class="fn">CreateDevice</span>(<span class="type">VkDeviceCreateFlags</span> <span class="par">flags</span> = 0) {
    <span class="kw">float</span> queuePriority = 1.f;
    <span class="type">VkDeviceQueueCreateInfo</span> queueCreateInfos[3] = {
        {
            .sType = <span class="enum">VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO</span>,
            .queueCount = 1,
            .pQueuePriorities = &amp;queuePriority },
        {
            .sType = <span class="enum">VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO</span>,
            .queueCount = 1,
            .pQueuePriorities = &amp;queuePriority },
        {
            .sType = <span class="enum">VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO</span>,
            .queueCount = 1,
            .pQueuePriorities = &amp;queuePriority } };
    <span class="type">uint32_t</span> queueCreateInfoCount = 0;
    <span class="kw">if</span> (queueFamilyIndex_graphics != <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>)
        queueCreateInfos[queueCreateInfoCount++].queueFamilyIndex = queueFamilyIndex_graphics;
    <span class="kw">if</span> (queueFamilyIndex_presentation != <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span> &amp;&amp;
        queueFamilyIndex_presentation != queueFamilyIndex_graphics)
        queueCreateInfos[queueCreateInfoCount++].queueFamilyIndex = queueFamilyIndex_presentation;
    <span class="kw">if</span> (queueFamilyIndex_compute != <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span> &amp;&amp;
        queueFamilyIndex_compute != queueFamilyIndex_graphics &amp;&amp;
        queueFamilyIndex_compute != queueFamilyIndex_presentation)
        queueCreateInfos[queueCreateInfoCount++].queueFamilyIndex = queueFamilyIndex_compute;
    <span class="cmt">/*变更*/</span><span class="fn">GetPhysicalDeviceFeatures</span>();
    <span class="type">VkDeviceCreateInfo</span> deviceCreateInfo = {
        .sType = <span class="enum">VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO</span>,
        .pNext = pNext_deviceCreateInfo, <span class="cmt">//新增，不过一会儿还会删掉</span>
        .flags = <span class="par">flags</span>,
        .queueCreateInfoCount = queueCreateInfoCount,
        .pQueueCreateInfos = queueCreateInfos,
        .enabledExtensionCount = <span class="type">uint32_t</span>(deviceExtensions.<span class="fn">size</span>()),
        .ppEnabledExtensionNames = deviceExtensions.<span class="fn">data</span>(),
        .pEnabledFeatures = &amp;physicalDeviceFeatures
    };
    <span class="kw">if</span> (<span class="type">VkResult</span> result = <span class="fn">vkCreateDevice</span>(physicalDevice, &amp;deviceCreateInfo, <span class="kw">nullptr</span>, &amp;device)) {
        outStream &lt;&lt; std::<span class="fn">format</span>(<span class="str">&quot;[ graphicsBase ] ERROR\nFailed to create a vulkan logical device!\nError code: {}\n&quot;</span>, <span class="type">int32_t</span>(result));
        <span class="kw">return</span> result;
    }
    <span class="kw">if</span> (queueFamilyIndex_graphics != <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>)
        <span class="fn">vkGetDeviceQueue</span>(device, queueFamilyIndex_graphics, 0, &amp;queue_graphics);
    <span class="kw">if</span> (queueFamilyIndex_presentation != <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>)
        <span class="fn">vkGetDeviceQueue</span>(device, queueFamilyIndex_presentation, 0, &amp;queue_presentation);
    <span class="kw">if</span> (queueFamilyIndex_compute != <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>)
        <span class="fn">vkGetDeviceQueue</span>(device, queueFamilyIndex_compute, 0, &amp;queue_compute);
    <span class="cmt">/*变更*/</span><span class="fn">GetPhysicalDeviceProperties</span>();
    <span class="cmt">//下一行的physicalDeviceProperties.deviceName变成了physicalDeviceProperties.properties.deviceName</span>
    outStream &lt;&lt; std::<span class="fn">format</span>(<span class="str">&quot;Renderer: {}\n&quot;</span>, physicalDeviceProperties.properties.deviceName);
    <span class="kw">for</span> (<span class="kw">auto</span>&amp; i : callbacks_createDevice)
        i();
    <span class="kw">return</span> <span class="enum">VK_SUCCESS</span>;
}
</pre>
<p>
    事情没完，标准中规定：如果开启的设备特性由<span class="type">VkPhysicalDeviceFeatures2</span>而非<span class="type">VkPhysicalDeviceFeatures</span>指定，那么<span class="type">VkPhysicalDeviceFeatures2</span>被接在<span class="type">VkDeviceCreateInfo</span>的pNext链中，而<span class="type">VkDeviceCreateInfo</span>::pEnabledFeatures必须为空指针：
</p>
<pre class="code">
<span class="type">VkResult</span> <span class="fn">CreateDevice</span>(<span class="type">VkDeviceCreateFlags</span> <span class="par">flags</span> = 0) {
    <span class="cmt">/*...*/</span>
    <span class="fn">GetPhysicalDeviceFeatures</span>();
    <span class="type">VkDeviceCreateInfo</span> deviceCreateInfo = {
        .sType = <span class="enum">VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO</span>,
        <span class="cmt">//.pNext = pNext_deviceCreateInfo, //若pNext_deviceCreateInfo为nullptr，在此赋值没有意义，到执行SetPNext(...)后再赋值</span>
        .flags = <span class="par">flags</span>,
        .queueCreateInfoCount = queueCreateInfoCount,
        .pQueueCreateInfos = queueCreateInfos,
        .enabledExtensionCount = <span class="type">uint32_t</span>(deviceExtensions.<span class="fn">size</span>()),
        .ppEnabledExtensionNames = deviceExtensions.<span class="fn">data</span>(),
        <span class="cmt">//.pEnabledFeatures = &amp;physicalDeviceFeatures</span>
    };
    <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_1</span>)
        <span class="sfn">SetPNext</span>(pNext_deviceCreateInfo, &amp;physicalDeviceFeatures);
    <span class="kw">else</span>
        deviceCreateInfo.pEnabledFeatures = &amp;physicalDeviceFeatures.features;
    deviceCreateInfo.pNext = pNext_deviceCreateInfo;
    <span class="kw">if</span> (<span class="type">VkResult</span> result = <span class="fn">vkCreateDevice</span>(physicalDevice, &amp;deviceCreateInfo, <span class="kw">nullptr</span>, &amp;device)) {
        outStream &lt;&lt; std::<span class="fn">format</span>(<span class="str">&quot;[ graphicsBase ] ERROR\nFailed to create a vulkan logical device!\nError code: {}\n&quot;</span>, <span class="type">int32_t</span>(result));
        <span class="kw">return</span> result;
    }
    <span class="cmt">/*...*/</span>
}
</pre>
<p>
    事情还是没完，上述代码有个隐藏的风险。
    <br>
    执行<code><span class="sfn">SetPNext</span>(pNext_deviceCreateInfo, &amp;physicalDeviceFeatures);</code>后，<span class="var">deviceCreateInfo</span>的pNext链变成了这么个顺序：
    <br>
    <span class="var">deviceCreateInfo</span>本身 -&gt; 调用<span class="fn">CreateDevice</span>(...)前以pNext_deviceCreateInfo起始的pNext链 -&gt; 设备特性结构体的链表
</p>
<p>
    考虑到重建逻辑设备的需要，<span class="fn">CreateDevice</span>(...)会被<span class="fn">RecreateDevice</span>(...)调用，然后再次执行<span class="fn">GetPhysicalDeviceFeatures</span>()。
    <br>
    可是，如果重建逻辑设备前在以pNext_deviceCreateInfo起始的pNext链最后接了新的结构体呢（基于开启某些显卡特定的功能等理由）？
    <br>
    按上述代码的写法，新的结构体会被接在设备特性结构体链表的最后，而<span class="fn">vkGetPhysicalDeviceFeatures2</span>(...)只接收与设备特性有关的的结构体，于是很可能运行出错。
</p>
<p>
    因此，有必要在创建逻辑设备后，再将设备特性结构体链表，从以pNext_deviceCreateInfo起始的pNext链中分离出来（这就是让<span class="sfn">SetPNext</span>(...)返回一个<span class="kw">void</span>**类型指针的原因）：
</p>
<pre class="code">
<span class="type">VkResult</span> <span class="fn">CreateDevice</span>(<span class="type">VkDeviceCreateFlags</span> <span class="par">flags</span> = 0) {
    <span class="cmt">/*...*/</span>
    <span class="fn">GetPhysicalDeviceFeatures</span>();
    <span class="type">VkDeviceCreateInfo</span> deviceCreateInfo = {
        .sType = <span class="enum">VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO</span>,
        <span class="cmt">//.pNext = pNext_deviceCreateInfo</span>,
        .flags = <span class="par">flags</span>,
        .queueCreateInfoCount = queueCreateInfoCount,
        .pQueueCreateInfos = queueCreateInfos,
        .enabledExtensionCount = <span class="type">uint32_t</span>(deviceExtensions.<span class="fn">size</span>()),
        .ppEnabledExtensionNames = deviceExtensions.<span class="fn">data</span>(),
        <span class="cmt">//.pEnabledFeatures = &amp;physicalDeviceFeatures</span>
    };
    <span class="kw">void</span>** ppNext = <span class="kw">nullptr</span>;
    <span class="kw">if</span> (apiVersion &gt;= <span class="mcr">VK_API_VERSION_1_1</span>)
        ppNext = <span class="sfn">SetPNext</span>(pNext_deviceCreateInfo, &amp;physicalDeviceFeatures);
    <span class="kw">else</span>
        deviceCreateInfo.pEnabledFeatures = &amp;physicalDeviceFeatures.features;
    deviceCreateInfo.pNext = pNext_deviceCreateInfo;
    <span class="type">VkResult</span> result = <span class="fn">vkCreateDevice</span>(physicalDevice, &amp;deviceCreateInfo, <span class="kw">nullptr</span>, &amp;device)
    <span class="kw">if</span> (ppNext)
        *ppNext = <span class="kw">nullptr</span>;
    <span class="kw">if</span> (result) {
        outStream &lt;&lt; std::<span class="fn">format</span>(<span class="str">&quot;[ graphicsBase ] ERROR\nFailed to create a vulkan logical device!\nError code: {}\n&quot;</span>, <span class="type">int32_t</span>(result));
        <span class="kw">return</span> result;
    }
    <span class="cmt">/*...*/</span>
}
</pre></section>
</section>


           </div>
          </div>
          <footer><div class="rst-footer-buttons" role="navigation" aria-label="Footer">
        <a href="Ch5-3%202D%E8%B4%B4%E5%9B%BE%E6%95%B0%E7%BB%84.html" class="btn btn-neutral float-left" title="Ch5-3 2D贴图数组" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left" aria-hidden="true"></span> 上一页</a>
        <a href="Ch6-1%20%E6%97%A0%E5%9B%BE%E5%83%8F%E5%B8%A7%E7%BC%93%E5%86%B2.html" class="btn btn-neutral float-right" title="Ch6-1 无图像帧缓冲" accesskey="n" rel="next">下一页 <span class="fa fa-arrow-circle-right" aria-hidden="true"></span></a>
    </div>

  <hr/>

  <div role="contentinfo">
    <p>&#169; 版权所有 2021-2025, 葉橙.</p>
  </div>

  利用 <a href="https://www.sphinx-doc.org/">Sphinx</a> 构建，使用了 
    <a href="https://github.com/readthedocs/sphinx_rtd_theme">主题</a>
    由 <a href="https://readthedocs.org">Read the Docs</a>开发.
   

</footer>
        </div>
      </div>
    </section>
  </div>
  <script>
      jQuery(function () {
          SphinxRtdTheme.Navigation.enable(true);
      });
  </script> 

</body>
</html>